Where and Why are people generating iNaturalist data in Boulder County?
iNaturalist is a mobile application that allows people to take photos of plants or animals they encounter in their environment and returns the identification of that species so people can learn more about those species. The user takes a photo of any species they encounter and upload that photo to the cloud, along with the metadata for the time and location when the photo was taken. Cloud computation services then use an AI classification algorithm to identify a list of candidate species matching the photo and location and return those result to the user for verification. The verified observation is then added to an iNaturalist databases that already includes over 68 Million individual occurrence records after only a few years of operation. These records are available through an iNaturalist API, but those records are also dumped into the GBIF database, where they undergo a small round of quality control that clean up the data in a few minor ways. I access these data using the ‘spocc’ package in R, which consolidates the API protocols for several different species occurrence API services into a single API syntax housed in one package. The ‘spocc’ package also interacts natively with the ‘taxis’ package to clean and standardize species names and common names across all taxa.
iNaturalist API
This code is an example showing how to pull data for a single species from the GBIF database. This is a great way to get data for an individual species, but GBIF suggests you use their online tool, rather than their API, for downloading multiple species at one time. You can set up a for loop to automate a list of species, but that unnecessarily drives up maintenance costs for GBIF, and they will again point you back to the online tool.
(df <- occ(query = 'Sciurus carolinensis', from = 'gbif', has_coords = TRUE))
df2 <- occ2df(df)
class(df2)
df3 <- dframe(df2) %>% scrubr::fix_names( how = 'shortest')
df3 <- df3[df3$latitude > 45 & df3$longitude > -74 & df3$longitude < -73 ,,drop=TRUE]
df3
Hexagon grid to aggregate data for standardized analysis
Sampling scheme is a critical component to any analysis. I sample the points above by laying a hexagonal grid over the occurrence points and aggregating those points into those hex bins for modeling. For a 2D spatial analysis like the one we’re doing here, I prefer a hexagon lattice over a square lattice because it has a more symmetrical adjacency matrix. In a square lattice, the diagonals between cells are longer than the horizontal and vertical distances. In hexagonal grids, all adjacency distances are equal. This code creates the hex grid to be used below for sampling. The extent of this grid is set by the Boulder County bounding box and he resolution was set to be rather course to speed up computation time and keep this example light and fast.
# Define bounding box
ext <- as(extent(getbb ("Boulder County Colorado")) , "SpatialPolygons")
# Set spatial projection
crs(ext) <- "EPSG:4326"
# Use spsample to measure out a grid of center points within the bounding box
h <- spsample(ext, type = "hexagonal", cellsize = 0.01)
# convert center points to hexagons
g <- HexPoints2SpatialPolygons(h, dx = 0.01)
# Reformat for easy plotting
g <- st_as_sf(g)
# Plot to check that it's built corrrectly
plot(g, lwd=0.1)

# Make the dataset more presentable
hex_polys <- cbind(seq(1, length(g$geometry)), g)
colnames(hex_polys) <- c("id_polygons", "geometry") # change colnames
Aggregate iNaturalist data into hex bins
Now that we have iNaturalist data formatted as points and a hex grid built for sampling those points, it’s time to bin those points into their appropriate hexagons. I do this using an intersect function that builds a dataframe by assigning each occurrence point to the hexagonal polygon that encompasses it’s coordinates. I then group those data by their new polygon identifier and plot the hexagon grid with a fill color gradient representing the number of unique occurrence points in that polygon.
Inspect the points overlaying the grid.
st_crs(st_GBIF) <- crs(hex_polys)
intersection <- st_intersection(x = hex_polys, y = st_GBIF)
int_result <- intersection %>%
group_by(id_polygons)
plot(g, lwd=0.1)
plot(int_result$geometry, pch=19, cex=0.25, col=adjustcolor("cornflowerblue", alpha.f = 0.2), add=TRUE)

Aggrigate points into hex bins
species_richness <- rep(0, max(hex_polys$id_polygons))
hex_counts <- cbind(hex_polys, species_richness)
int_count <- intersection %>%
group_by(id_polygons, .drop = FALSE) %>%
count()
hex_counts$species_richness[int_count$id_polygons] <- int_count$n
plot(hex_counts$geometry, lwd=0.001,
col=grey.colors(max(hex_counts$species_richness), rev = TRUE, start=0, end=1)[hex_counts$species_richness+1])
plot(int_result$geometry, pch=19, cex=0.05, col=adjustcolor("cornflowerblue", alpha.f = 0.2), add=TRUE)

Plot species richness using a fancy color scheme that works well for color-blind people.
A count of the number of species in an area is called species richness. Species richness is a common proxy variable used to describe differences between ecosystems or health within a single ecosystem. It provides an incomplete description of an ecosystem, but species richness is still frequently used as a good indicator of ecosystem type and health because more complex ecosystems have more species and so, the number of species can often approximate complexity and complexity is a defining feature of ecosystems.
plot(hex_counts$geometry, lwd=0.001,
col=hcl.colors(max(hex_counts$species_richness)+2, palette = "viridis", alpha = NULL, rev = TRUE, fixup = TRUE)[hex_counts$species_richness+1])

Adjacency matrix
We need to include a list of neighbor relationships as a random co-variate in our model. Her we calculate that adjacency matrix using a function that takes a list of polygons and returns the adjacency matrix. I give this function the hexagon grid, which is built of hexagonal polygons with centroids, and the function calculates the adjacency relationships for all those centroids.
hex_adj <- poly2nb(as(hex_counts, "Spatial") )
plot(hex_counts$geometry, lwd=0.001,
col=hcl.colors(max(hex_counts$species_richness)+2, palette = "viridis", alpha = NULL, rev = TRUE, fixup = TRUE)[hex_counts$species_richness+1])
plot(hex_adj, coordinates(as(hex_counts, "Spatial")), col="darkblue", lwd=0.2, add=TRUE)

Co-variates
Data don’t produce themselves in a vacuum, they are created by a process and that process involves many parts. I am now going to organize some data to act as co-variates in a model predicting iNaturalist species richness. These variables should relate to hypotheses about mechanisms that created these data and they should be at the same spatial and temporal scale as other data in the analysis.
Open Street Map data
On of my favorite datasets to use on a daily basis is Open Street Map. It is an amazing resource for a suite of different data about land use, amenities, administrative boundaries, points of interest, routing, and construction. The code below pulls polygons and multipolygons from the OSM API based on value/key pairs. I picked each value/key pair using the OSM tags wiki website, which catalogs all the available data in OSM. I search for different tags that described green space and I wrote a new API call for each value/key pair that I thought applicable. I concatenate those individual data requests into a single green space dataset. I then make three more individual calls to retrive data for buildings, roads, and agricultural fields. I think each of these may be contributing to our response variable so, I have left as individual dataframes so they can be treated individually in the modeling process.
name <- "Boulder County Colorado"
try(my_bbox <- osmdata::getbb (name))
my_bbox[1,1] <- my_bbox[1,1] + 0.02
dat1 <- opq(bbox = my_bbox, timeout = 900) %>%
add_osm_feature(key = 'leisure', value = 'park') %>%
osmdata_sf ()
dat2 <- opq(bbox = my_bbox, timeout = 900) %>%
add_osm_feature(key = 'leisure', value = 'garden') %>%
osmdata_sf ()
dat3 <- opq(bbox = my_bbox, timeout = 900) %>%
add_osm_feature(key = 'leisure', value = 'nature_reserve') %>%
osmdata_sf ()
dat4 <- opq(bbox = my_bbox, timeout = 900) %>%
add_osm_feature(key = 'leisure', value = 'dog_park') %>%
osmdata_sf ()
dat5 <- opq(bbox = my_bbox, timeout = 900) %>%
add_osm_feature(key = 'leisure', value = 'common') %>%
osmdata_sf ()
dat6 <- opq(bbox = my_bbox, timeout = 900) %>%
add_osm_feature(key = 'leisure', value = 'pitch') %>%
osmdata_sf ()
dat7 <- opq(bbox = my_bbox, timeout = 900) %>%
add_osm_feature(key = 'leisure', value = 'horse_riding') %>%
osmdata_sf ()
dat8 <- opq(bbox = my_bbox, timeout = 900) %>%
add_osm_feature(key = 'natural', value = 'wood') %>%
osmdata_sf ()
greens <- c (dat1, dat2, dat3, dat4, dat5, dat6, dat7, dat8)
buildings <- opq(bbox = my_bbox, timeout = 900) %>%
add_osm_feature(key = 'building') %>%
osmdata_sf ()
roads <- opq(bbox = my_bbox, timeout = 900) %>%
add_osm_feature(key = 'landuse', value = 'residential') %>%
osmdata_sf ()
agriculture <- opq(bbox = my_bbox, timeout = 900) %>%
add_osm_feature(key = 'landuse', value = 'farmland') %>%
osmdata_sf()
Plot the green space data downloaded from OSM against the species richness data to make sure they overlap well.
plot(hex_counts$geometry, lwd=0.001,
col=hcl.colors(max(hex_counts$species_richness), palette = "viridis", alpha = 0, rev = TRUE, fixup = TRUE)[hex_counts$species_richness+1])
plot(greens$osm_polygons[1], add=TRUE, lwd=0.01, col=adjustcolor("grey", alpha.f = 1))
plot(greens$osm_multipolygons[1], add=TRUE, lwd=0.01, col=adjustcolor("grey", alpha.f = 1))
#plot(buildings$osm_polygons[1], add=TRUE, lwd=0.01, col=adjustcolor("black", alpha.f = 1))
plot(hex_counts$geometry, lwd=0.001,
col=hcl.colors(max(hex_counts$species_richness)+2, palette = "viridis", alpha = 0.8, rev = TRUE, fixup = TRUE)[hex_counts$species_richness+1], add=TRUE)

Aggregate covariates to hex bins using the same method as we did with species richness
Aggregating data to hexagon bins will be a required procedure for all covariates, which means we will need to repeat this procedure and it make sense to stop and make a function to keep our code clean. The first of these functions calculates polygon area for the greenspace polygons and adds that area calculation back into the dataset as a covariate.
covariate_hex_greens <- function(sf_object){
if(length(sf_object$osm_multipolygons) > 0){
poly_area <- st_area(sf_object$osm_polygons)
multi_poly_area <- st_area(sf_object$osm_multipolygons)
log_area1 <- as.data.frame(as.numeric(log(poly_area)))
log_area2 <- as.data.frame(as.numeric(log(multi_poly_area)))
names(log_area1) <- "log_area"
names(log_area2) <- "log_area"
log_area <- rbind(log_area1, log_area2)
coords <- rbind(sf_object$osm_polygons %>% st_centroid() %>% st_coordinates(),
sf_object$osm_multipolygons %>% st_centroid() %>% st_coordinates())
} else {
poly_area <- st_area(sf_object$osm_polygons)
log_area <- as.data.frame(as.numeric(log(poly_area)))
names(log_area) <- "log_area"
coords <- sf_object$osm_polygons %>% st_centroid() %>% st_coordinates()
}
if(length(coords[,1]) != length(log_area[,1])) stop("wrong length")
area_points <- cbind(log_area, coords)
colnames(area_points)[2:3] <- c("longitude", "latitude")
sf_area_points <- st_as_sf(area_points, coords = c("longitude", "latitude"),
crs = 4326, agr = "constant")
return(sf_area_points)
}
The second function does the same aggregation procedure as I did with species richness, which is to say that it counts then number of occurrences within a bin and assigns that bin a value for that count. The other function in this chunk is a clone of the counting function modified to report mean rather than count. The mean version of the function is valuable or the greenspace area data where reporting the mean area of greenspace if more informative than the count of parks in that bin.
# Count the number of points in each bin
point_to_hex_count <- function(hex_polys, points){
intersection <- st_intersection(x = hex_polys, y = points)
counter <- rep(0, max(hex_polys$id_polygons))
hex_count <- cbind(hex_polys, counter)
counted <- intersection %>%
group_by(id_polygons, .drop = FALSE) %>%
count()
hex_count$area[counted$id_polygons] <- counted$n
return(hex_count)
}
# Report the mean value for each bin
point_to_hex_mean <- function(hex_polys, points){
intersection <- st_intersection(x = hex_polys, y = points)
area <- rep(0, max(hex_polys$id_polygons))
hex_area <- cbind(hex_polys, area)
avg_area <- intersection %>%
group_by(id_polygons, .drop = FALSE) %>%
summarise(mean = sum(log_area, na.rm=TRUE))
hex_area$area[avg_area$id_polygons] <- avg_area$mean
return(hex_area)
}
Run those functions on our green space data to produce a plot of green space area intensity
The plot below show the total area of greenspace per polygon. My hypothesis is that the presence of green space is a major predictor of iNaturalist data and want to compare this plot to the species richness plot to see it it looks like green spaces and iNaturalist records are generally located in the same places.
green_pts <- covariate_hex_greens(greens)
green_hex <- point_to_hex_mean(hex_polys, green_pts)
hex <- green_hex
plot_variable <- function(hex, variable ){
plot(hex$geometry, lwd=0.001,
col=hcl.colors(max(as.integer(green_hex$area)+2, na.rm = TRUE), palette = "viridis", alpha = NULL, rev = TRUE, fixup = TRUE)[as.integer(green_hex$area, na.rm=TRUE)+1])
}
plot_variable(green_hex, green_hex$area)

Combine Species richness and covariates into the same dataframe for analysis.
In the previous code, I have created two spatial objects that are both hex grids with aggregated data assigned to each bin. To combine those sets, I first drop the geometry from one of the two because the geometries are identical and they will show up as duplicates in the combined dataset. After droping the geometry from one, I do a left join so they match polygonID values and then everything shares the single remaining geometry column.
```r
hex_list <- st_drop_geometry(hex_counts) %>% inner_join(as.data.frame(hex_green_area), by=\id_polygons\)
colnames(hex_list) <- c(\id_polygon\, \species_richness\, \park_size_average\,\geometry\)
hex_list
<!-- rnb-source-end -->
<!-- rnb-chunk-end -->
<!-- rnb-text-begin -->
### INLA Bayesian inference
<!-- rnb-text-end -->
<!-- rnb-chunk-begin -->
<!-- rnb-source-begin eyJkYXRhIjoiYGBgclxuYGBgclxuXG5oZXhfbGlzdCRsb2dfcGFya19hcmVhIDwtIGxvZyhoZXhfbGlzdCRwYXJrX3NpemVfYXZlcmFnZSlcbmhleF9saXN0W3doaWNoKGlzLmluZmluaXRlKGhleF9saXN0JGxvZ19wYXJrX2FyZWEpID09IFRSVUUpLFxcbG9nX3BhcmtfYXJlYVxcXSA8LSAwXG5cbiNSVzJkXG5tMC5ydzJkIDwtIGlubGEoc3BlY2llc19yaWNobmVzcyB+IGxvZ19wYXJrX2FyZWEgK1xuICAgIGYoaWRfcG9seWdvbiwgbW9kZWwgPSBcXHJ3MmRcXCwgbnJvdyA9NjQsIG5jb2wgPSA0MSksXG4gIGZhbWlseSA9IFxccG9pc3NvblxcLCBkYXRhID0gYXMuZGF0YS5mcmFtZShoZXhfbGlzdCksXG4gIGNvbnRyb2wucHJlZGljdG9yID0gbGlzdChjb21wdXRlID0gVFJVRSksXG4gIGNvbnRyb2wuY29tcHV0ZSA9IGxpc3QoZGljID0gVFJVRSkgKVxuXG5zdW1tYXJ5KG0wLnJ3MmQpXG5cbmhleF9saXN0JFJXMkQgPC0gbTAucncyZCRzdW1tYXJ5LmZpdHRlZC52YWx1ZXNbLCBcXDAuNXF1YW50XFxdXG5oZXhfbGlzdCRSVzJEc2QgPC0gbTAucncyZCRzdW1tYXJ5LmZpdHRlZC52YWx1ZXNbLCBcXHNkXFxdXG5gYGBcbmBgYCJ9 -->
```r
```r
hex_list$log_park_area <- log(hex_list$park_size_average)
hex_list[which(is.infinite(hex_list$log_park_area) == TRUE),\log_park_area\] <- 0
#RW2d
m0.rw2d <- inla(species_richness ~ log_park_area +
f(id_polygon, model = \rw2d\, nrow =64, ncol = 41),
family = \poisson\, data = as.data.frame(hex_list),
control.predictor = list(compute = TRUE),
control.compute = list(dic = TRUE) )
summary(m0.rw2d)
hex_list$RW2D <- m0.rw2d$summary.fitted.values[, \0.5quant\]
hex_list$RW2Dsd <- m0.rw2d$summary.fitted.values[, \sd\]
<!-- rnb-source-end -->
<!-- rnb-chunk-end -->
<!-- rnb-text-begin -->
### Plot the mean predicted values from the model.
<!-- rnb-text-end -->
<!-- rnb-chunk-begin -->
<!-- rnb-source-begin eyJkYXRhIjoiYGBgclxuYGBgclxuI3Bsb3RfdmFyaWFibGUoaGV4X2xpc3QsIGZsb29yKGhleF9saXN0JFJXMkQpKVxuXG5tb2RlbF9wcmVkaWN0IDwtIGFzLmludGVnZXIoaGV4X2xpc3QkUlcyRClcbm1vZGVsX3ByZWRpY3Rbd2hpY2goaXMubmEobW9kZWxfcHJlZGljdCkgPT0gVFJVRSldIDwtIDAgXG4jbW9kZWxfcHJlZGljdFt3aGljaChtb2RlbF9wcmVkaWN0IDwgMCldIDwtIDAgXG4jbW9kZWxfcHJlZGljdFt3aGljaChtb2RlbF9wcmVkaWN0ID4gMjApXSA8LSAwIFxuXG5wbG90KGhleF9jb3VudHMkZ2VvbWV0cnksIGx3ZD0wLjAwMSwgXG4gICAgIGNvbD1oY2wuY29sb3JzKG1heChoZXhfY291bnRzJHNwZWNpZXNfcmljaG5lc3MpKzIsIHBhbGV0dGUgPSBcXHZpcmlkaXNcXCwgYWxwaGEgPSBOVUxMLCByZXYgPSBUUlVFLCBmaXh1cCA9IEZBTFNFKVtoZXhfY291bnRzJHNwZWNpZXNfcmljaG5lc3MrMV0pXG5cbnBsb3QoaGV4X2xpc3QkZ2VvbWV0cnksIGx3ZD0wLjAwMSwgXG4gICAgIGNvbD1oY2wuY29sb3JzKG1heChoZXhfY291bnRzJHNwZWNpZXNfcmljaG5lc3MpKzIsIHBhbGV0dGUgPSBcXHZpcmlkaXNcXCwgYWxwaGEgPSBOVUxMLCByZXYgPSBUUlVFLCBmaXh1cCA9IEZBTFNFKVttb2RlbF9wcmVkaWN0KzFdKVxuI3Bsb3QoaW50X3Jlc3VsdCRnZW9tZXRyeSwgcGNoPTE5LCBjZXg9MC4wNSwgY29sPWFkanVzdGNvbG9yKFxcY29ybmZsb3dlcmJsdWVcXCwgYWxwaGEuZiA9IDAuMiksIGFkZD1UUlVFKVxuYGBgXG5gYGAifQ== -->
```r
```r
#plot_variable(hex_list, floor(hex_list$RW2D))
model_predict <- as.integer(hex_list$RW2D)
model_predict[which(is.na(model_predict) == TRUE)] <- 0
#model_predict[which(model_predict < 0)] <- 0
#model_predict[which(model_predict > 20)] <- 0
plot(hex_counts$geometry, lwd=0.001,
col=hcl.colors(max(hex_counts$species_richness)+2, palette = \viridis\, alpha = NULL, rev = TRUE, fixup = FALSE)[hex_counts$species_richness+1])
plot(hex_list$geometry, lwd=0.001,
col=hcl.colors(max(hex_counts$species_richness)+2, palette = \viridis\, alpha = NULL, rev = TRUE, fixup = FALSE)[model_predict+1])
#plot(int_result$geometry, pch=19, cex=0.05, col=adjustcolor(\cornflowerblue\, alpha.f = 0.2), add=TRUE)
<!-- rnb-source-end -->
<!-- rnb-chunk-end -->
<!-- rnb-text-begin -->
<!-- rnb-text-end -->
<!-- rnb-chunk-begin -->
<!-- rnb-source-begin eyJkYXRhIjoiYGBgclxuYGBgclxuYmVpLnRyZWVzMiRSVzJEIDwtIG0wLnJ3MmQkc3VtbWFyeS5maXR0ZWQudmFsdWVzWywgXFxtZWFuXFxdXG5cbmJlaS50cmVlczIkTUFURVJOMkQgPC0gbTAubTJkJHN1bW1hcnkuZml0dGVkLnZhbHVlc1ssIFxcbWVhblxcXVxuYGBgXG5gYGAifQ== -->
```r
```r
bei.trees2$RW2D <- m0.rw2d$summary.fitted.values[, \mean\]
bei.trees2$MATERN2D <- m0.m2d$summary.fitted.values[, \mean\]
<!-- rnb-source-end -->
<!-- rnb-chunk-end -->
<!-- rnb-text-begin -->
<!-- rnb-text-end -->
<!-- rnb-chunk-begin -->
<!-- rnb-source-begin eyJkYXRhIjoiYGBgclxuYGBgclxuZ19zZiA8LSBzdF9hc19zZihnKVxuSFEgPC0gcmFzdGVyOjpleHRyYWN0KHJhc3Rlcl90aGlzX0hRLCAgZ19zZiAsIGZ1biA9IHN1bSwgbmEucm0gPSBUUlVFLCBkZiA9IFRSVUUsIG5vcm1hbGl6ZVdlaWdodHM9VFJVRSkgIFxuYGBgXG5gYGAifQ== -->
```r
```r
g_sf <- st_as_sf(g)
HQ <- raster::extract(raster_this_HQ, g_sf , fun = sum, na.rm = TRUE, df = TRUE, normalizeWeights=TRUE)
```
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICBib29rZG93bjo6cGRmX2Jvb2s6CiAgICBiYXNlX2Zvcm1hdDogcnRpY2xlczo6anNzX2FydGljbGUgIAotLS0KCgpgYGB7ciwgY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZScsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoInJnZGFsIikgCmxpYnJhcnkoInNmIikKbGlicmFyeSgndGlkeXZlcnNlJykKbGlicmFyeSgnc3BvY2MnKQpsaWJyYXJ5KCdtYXByJykKbGlicmFyeSgic2NydWJyIikKbGlicmFyeSgib3NtZGF0YSIpCmxpYnJhcnkoInJhc3RlciIpCmxpYnJhcnkoIm9zbWRhdGEiKQpsaWJyYXJ5KCJ0YXhpemUiKQpsaWJyYXJ5KCJJTkxBIikKbGlicmFyeSgiZGF0YS50YWJsZSIpCmxpYnJhcnkoInNwIikKbGlicmFyeSgic3BkZXAiKQpgYGAKCgojIFdoZXJlIGFuZCBXaHkgYXJlIHBlb3BsZSBnZW5lcmF0aW5nIGlOYXR1cmFsaXN0IGRhdGEgaW4gQm91bGRlciBDb3VudHk/CgogIGlOYXR1cmFsaXN0IGlzIGEgbW9iaWxlIGFwcGxpY2F0aW9uIHRoYXQgYWxsb3dzIHBlb3BsZSB0byB0YWtlIHBob3RvcyBvZiBwbGFudHMgb3IgYW5pbWFscyB0aGV5IGVuY291bnRlciBpbiB0aGVpciBlbnZpcm9ubWVudCBhbmQgcmV0dXJucyB0aGUgaWRlbnRpZmljYXRpb24gb2YgdGhhdCBzcGVjaWVzIHNvIHBlb3BsZSBjYW4gbGVhcm4gbW9yZSBhYm91dCB0aG9zZSBzcGVjaWVzLiBUaGUgdXNlciB0YWtlcyBhIHBob3RvIG9mIGFueSBzcGVjaWVzIHRoZXkgZW5jb3VudGVyIGFuZCB1cGxvYWQgdGhhdCBwaG90byB0byB0aGUgY2xvdWQsIGFsb25nIHdpdGggdGhlIG1ldGFkYXRhIGZvciB0aGUgdGltZSBhbmQgbG9jYXRpb24gd2hlbiB0aGUgcGhvdG8gd2FzIHRha2VuLiBDbG91ZCBjb21wdXRhdGlvbiBzZXJ2aWNlcyB0aGVuIHVzZSBhbiBBSSBjbGFzc2lmaWNhdGlvbiBhbGdvcml0aG0gdG8gaWRlbnRpZnkgYSBsaXN0IG9mIGNhbmRpZGF0ZSBzcGVjaWVzIG1hdGNoaW5nIHRoZSBwaG90byBhbmQgbG9jYXRpb24gYW5kIHJldHVybiB0aG9zZSByZXN1bHQgdG8gdGhlIHVzZXIgZm9yIHZlcmlmaWNhdGlvbi4gVGhlIHZlcmlmaWVkIG9ic2VydmF0aW9uIGlzIHRoZW4gYWRkZWQgdG8gYW4gaU5hdHVyYWxpc3QgZGF0YWJhc2VzIHRoYXQgYWxyZWFkeSBpbmNsdWRlcyBvdmVyIDY4IE1pbGxpb24gaW5kaXZpZHVhbCBvY2N1cnJlbmNlIHJlY29yZHMgYWZ0ZXIgb25seSBhIGZldyB5ZWFycyBvZiBvcGVyYXRpb24uIFRoZXNlIHJlY29yZHMgYXJlIGF2YWlsYWJsZSB0aHJvdWdoIGFuIGlOYXR1cmFsaXN0IEFQSSwgYnV0IHRob3NlIHJlY29yZHMgYXJlIGFsc28gZHVtcGVkIGludG8gdGhlIEdCSUYgZGF0YWJhc2UsIHdoZXJlIHRoZXkgdW5kZXJnbyBhIHNtYWxsIHJvdW5kIG9mIHF1YWxpdHkgY29udHJvbCB0aGF0IGNsZWFuIHVwIHRoZSBkYXRhIGluIGEgZmV3IG1pbm9yIHdheXMuIEkgYWNjZXNzIHRoZXNlIGRhdGEgdXNpbmcgdGhlICdzcG9jYycgcGFja2FnZSBpbiBSLCB3aGljaCBjb25zb2xpZGF0ZXMgdGhlIEFQSSBwcm90b2NvbHMgZm9yIHNldmVyYWwgZGlmZmVyZW50IHNwZWNpZXMgb2NjdXJyZW5jZSBBUEkgc2VydmljZXMgaW50byBhIHNpbmdsZSBBUEkgc3ludGF4IGhvdXNlZCBpbiBvbmUgcGFja2FnZS4gVGhlICdzcG9jYycgcGFja2FnZSBhbHNvIGludGVyYWN0cyBuYXRpdmVseSB3aXRoIHRoZSAndGF4aXMnIHBhY2thZ2UgdG8gY2xlYW4gYW5kIHN0YW5kYXJkaXplIHNwZWNpZXMgbmFtZXMgYW5kIGNvbW1vbiBuYW1lcyBhY3Jvc3MgYWxsIHRheGEuIAogIAojIyMgaU5hdHVyYWxpc3QgQVBJCiAgVGhpcyBjb2RlIGlzIGFuIGV4YW1wbGUgc2hvd2luZyBob3cgdG8gcHVsbCBkYXRhIGZvciBhIHNpbmdsZSBzcGVjaWVzIGZyb20gdGhlIEdCSUYgZGF0YWJhc2UuIFRoaXMgaXMgYSBncmVhdCB3YXkgdG8gZ2V0IGRhdGEgZm9yIGFuIGluZGl2aWR1YWwgc3BlY2llcywgYnV0IEdCSUYgc3VnZ2VzdHMgeW91IHVzZSB0aGVpciBvbmxpbmUgdG9vbCwgcmF0aGVyIHRoYW4gdGhlaXIgQVBJLCBmb3IgZG93bmxvYWRpbmcgbXVsdGlwbGUgc3BlY2llcyBhdCBvbmUgdGltZS4gWW91IGNhbiBzZXQgdXAgYSBmb3IgbG9vcCB0byBhdXRvbWF0ZSBhIGxpc3Qgb2Ygc3BlY2llcywgYnV0IHRoYXQgdW5uZWNlc3NhcmlseSBkcml2ZXMgdXAgbWFpbnRlbmFuY2UgY29zdHMgZm9yIEdCSUYsIGFuZCB0aGV5IHdpbGwgYWdhaW4gcG9pbnQgeW91IGJhY2sgdG8gdGhlIG9ubGluZSB0b29sLiAKYGBge3IsIGV2YWw9RkFMU0V9CihkZiA8LSBvY2MocXVlcnkgPSAnU2NpdXJ1cyBjYXJvbGluZW5zaXMnLCBmcm9tID0gJ2diaWYnLCAgaGFzX2Nvb3JkcyA9IFRSVUUpKQpkZjIgPC0gb2NjMmRmKGRmKQpjbGFzcyhkZjIpCmRmMyA8LSBkZnJhbWUoZGYyKSAlPiUgc2NydWJyOjpmaXhfbmFtZXMoIGhvdyA9ICdzaG9ydGVzdCcpCgpkZjMgPC0gZGYzW2RmMyRsYXRpdHVkZSA+IDQ1ICAmIGRmMyRsb25naXR1ZGUgPiAtNzQgJiBkZjMkbG9uZ2l0dWRlIDwgLTczICwsZHJvcD1UUlVFXQpkZjMKYGBgCgojIyMgR0JJRiBvbmxpbmUgZG93bmxvYWQgdG9vbAogIEhlcmUgSSB1cGxvYWQgdGhlIGZpbGUgdGhhdCBJIGRvd25sb2FkZWQgZnJvbSBnYmlmLm9yZy4gVG8gY3JlYXRlIHRoaXMgZmlsZSwgSSB1c2VkIEdCSUYncyBvbmxpbmUgdG9vbCB0byBkcmF3IGEgYm91bmRpbmcgYm94IGFyb3VuZCBCb3VsZGVyIGNvdW50eSBhbmQgc3BlY2lmaWVkIHRoYXQgSSB3YW50ZWQgYWxsIGRhdGEgd2l0aCAoMSkgR2VvZ3JhcGhpYyBjb29yZGluYXRlcywgKDIpIE5vIGtub3duIGVycm9ycywgKDMpIHJlY29yZGVkIGluIDIwMjEsIGFuZCAoNCkgaU5hdHVyYWxpc3QgbGlzdGVkIGFzIHRoZWlyIHByb3ZpZGVyLiBUaGUgcHJvdmlkZSB0aGF0IGRhdGEgaW4gdGhlIGZvcm0gb2YgYSBDU1YsIHdoaWNoIEkgYWRkZWQgdG8gYSBmb2xkZXIgY2FsbGVkICJyYXdEYXRhIiBpbiB0aGUgd29ya2luZyBkaXJlY3RvcnkuIFRoZXNlIGRhdGEgY29tZSBhcyBhIGRhdGFmcmFtZSBvZiBvY2N1cnJlbmNlIHJlY29yZHMsIHdoaWNoIGVhY2ggcmVjb3JkIHRha2luZyBhIHJvdyBhbmQgZGlmZmVyZW50IHZhcmlhYmxlcyB0YWtpbmcgdGhlIGNvbHVtbnMuIFRoZSBmaXJzdCB0aGluZyBJIGRvIGlzIGNsZWFuIHRoZSBuYW1lcyB1c2luZyB0aGUgJ3NjcnVicicgcGFja2FnZS4gVGhlbiBJIHBsb3QgdGhlIHJhdyBkYXRhIHRvIG1ha2Ugc3VyZSB0aGV5IG1lZXQgbXkgZXhwZWN0YXRpb25zLiBJJ20gY2hlY2tpbmcgIGZvciBhbm9tYWxpZXMgdGhhdCB3b3VsZCBiZSBjYXVzZSBieSBpbXByb3BlciBsb2FkaW5nLCAgbGlrZSB0aGUgd3Jvbmcgc3BhdGlhbCBleHRlbnQgb3IgdW5leHBlY3RlZGx5IGVtcHR5IGZpZWxkcy4KICAKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGZpZy5jYXA9IlBsb3Qgb2YgcmF3IGlOYXR1cmFsaXN0IGRhdGEifQpnYmlmIDwtIGZyZWFkKCJyYXdEYXRhLzAyODkzNDQtMjAwNjEzMDg0MTQ4MTQzLmNzdiIsaGVhZGVyPVRSVUUpCkdCSUYgPC0gZ2JpZiAlPiUgc2NydWJyOjpmaXhfbmFtZXMoIGhvdyA9ICdzaG9ydGVzdCcpCgpwbG90KGdiaWYkZGVjaW1hbExhdGl0dWRlLCBnYmlmJGRlY2ltYWxMb25naXR1ZGUpCmBgYAoKSSBkaWQgYSBxdWljayBhdXRvbWF0ZWQgZGF0YSBjbGVhbiB1c2luZyB0aGUgJ3NjcnVicicgcGFja2FnZSwgYnV0IEkgd2FudGVkIHRvIGdvIGEgZmV3IHN0ZXBzIGZ1cnRoZXIgdG8gcmVwbGFjZSBzY2llbnRpZmljIG5hbWVzIHdpdGggY29tbW9uIG5hbWVzIHdoZW4gYXBwcm9wcmlhdGUuIENvbW1vbiBuYW1lcyBhcmUgY29tbW9uIGluIHNvbWUgZmllbGRzIGxpa2UgT3JuaXRob2xvZ3kgYW5kIE1hbW1hbG9neSwgYnV0IHRoZXkgYXJlIHVuY29tbW9uIGluIEJvdGFueSBhbmQgTWljcm9iaW9sb2d5LiBUaGUgY29ycmVjdGlvbnMgcHJvZHVjZWQgaW4gdGhpcyBzZWN0aW9uIHJlZmxlY3QgdGhvc2UgY3VzdG9tcyBhbmQgb25seSBmYXZvciBjb21tb24gbmFtZXMgZm9yIHNwZWNpZXMgd2hlcmUgdGhleSdyZSByZWd1bGFybHkgcHJvdmlkZWQgaW4gdGhlIGlOYXR1cmFsaXN0IGRhdGFzZXQuIEkgdGhpbm5lZCB0aGUgZGF0YXNldCB0byBpbmNsdWRlIG9ubHkgdW5pcXVlIGVudHJpZXMuIFRoaXMgd2FzIGxhcmdlbHkgYWJvdXQgbWFraW5nIHRoaXMgZXhhbXBsZSBzbWFsbGVyIGFuZCBlYXNpZXIgdG8gcnVuIHF1aWNrbHkuIFRoZXJlIGFyZSByZXBlYXRlZCByZWNvcmRzIGZyb20gc29tZSBub3RhYmxlIGluZGl2aWR1YWxzIGxpa2UgYmlyZHMgb2YgcHJleSBpbiBwdWJsaWMgcGFya3Mgb3Igbm90YWJsZSAgdHJlZXMgb24gcGVvcGxlJ3Mgd2FsayB0byB3b3JrLiBUaGVzZSBpbmRpdmlkdWFscyBhcmUgcmVjb3JkZWQgcmVndWxhcmx5IGFuZCByZXBlYXRlZGx5IGFuZCBjb3VsZCBiZSBpbmNsdWRlZCBpbiBhbiAgZXhhbXBsZSBsZXNzIHByZXNzZWQgZnJvbSBzcGFjZS4gICAgCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQojIFRoaW4gdG8gdW5pcXVlCmEgPC0gc2NpMmNvbW0oYXMuY2hhcmFjdGVyKHVuaXF1ZShHQklGJHZlcmJhdGltU2NpZW50aWZpY05hbWUpKSkKCiMgTWFrZSBhIGxpc3Qgb2YgbWF0Y2hlcyBiZXR3ZWVuIHNjaWVudGlmaWMgbmFtZXMgYW5kIGNvbW1vbiBuYW1lcwpjb21tb25fbmFtZXMgPC0gYXMuZGF0YS5mcmFtZSggdW5saXN0KGEpLCBieXJvdz1UUlVFLCBuY29sPTIpCmNvbW1vbl9uYW1lcyA8LSBjYmluZCggcm93bmFtZXMoY29tbW9uX25hbWVzKSwgY29tbW9uX25hbWVzKQpuYW1lcyhjb21tb25fbmFtZXMpIDwtIGMoInZlcmJhdGltU2NpZW50aWZpY05hbWUiLCJjb21tb25fbmFtZXMiKQp3cml0ZS5jc3YoY29tbW9uX25hbWVzLCBmaWxlPSJyYXdEYXRhL0NvbW1vbl9uYW1lcy5jc3YiKQoKIyBKb2luIHRoYXQgbGlzdCB0byB0aGUgZGF0YXNldApDb21tb25fR0JJRiA8LSBHQklGICU+JSBsZWZ0X2pvaW4oY29tbW9uX25hbWVzLCBieT0idmVyYmF0aW1TY2llbnRpZmljTmFtZSIpCkNvbW1vbl9HQklGIDwtIENvbW1vbl9HQklGWyxjKDUxLDE0LCAyMiwyMyApXSAKQ29tbW9uX0dCSUZbd2hpY2goaXMubmEoQ29tbW9uX0dCSUYkY29tbW9uX25hbWVzKSA9PSBUUlVFKSwxXSA8LSBDb21tb25fR0JJRlt3aGljaChpcy5uYShDb21tb25fR0JJRiRjb21tb25fbmFtZXMpID09IFRSVUUpLDJdCgojIEFkZCBjb29yZGluYXRlcwpjb29yZHMgPSBkYXRhLmZyYW1lKAogIHg9Q29tbW9uX0dCSUYkZGVjaW1hbExvbmdpdHVkZSwKICB5PUNvbW1vbl9HQklGJGRlY2ltYWxMYXRpdHVkZQopCgojIHJlZm9ybWF0IGZvciBzcGF0aWFsIGFuYWx5c2lzCkdCSUZfY29tbW9uX25hbWVzIDwtIGFzLmRhdGEuZnJhbWUoQ29tbW9uX0dCSUYkY29tbW9uX25hbWVzKQpuYW1lcyhHQklGX2NvbW1vbl9uYW1lcykgPC0gIkNvbW1vbl9uYW1lcyIKCnNwX0dCSUYgPC0gU3BhdGlhbFBvaW50c0RhdGFGcmFtZShjb29yZHMsR0JJRl9jb21tb25fbmFtZXMgKQpzdF9HQklGIDwtIHN0X2FzX3NmKFNwYXRpYWxQb2ludHNEYXRhRnJhbWUoY29vcmRzLEdCSUZfY29tbW9uX25hbWVzICkpCgojIFBsb3QgdG8gY2hlY2sgdGhhdCBldmVyeXRoaW5nIHdvcmtlZApwbG90KHN0X0dCSUYsIHBjaD0xOSwgY2V4PTAuMjUpCmBgYAoKIyMjIEhleGFnb24gZ3JpZCB0byBhZ2dyZWdhdGUgZGF0YSBmb3Igc3RhbmRhcmRpemVkIGFuYWx5c2lzCiAgU2FtcGxpbmcgc2NoZW1lIGlzIGEgY3JpdGljYWwgY29tcG9uZW50IHRvIGFueSBhbmFseXNpcy4gSSBzYW1wbGUgdGhlIHBvaW50cyBhYm92ZSBieSBsYXlpbmcgYSBoZXhhZ29uYWwgZ3JpZCBvdmVyIHRoZSBvY2N1cnJlbmNlIHBvaW50cyBhbmQgYWdncmVnYXRpbmcgdGhvc2UgcG9pbnRzIGludG8gdGhvc2UgaGV4IGJpbnMgZm9yIG1vZGVsaW5nLiBGb3IgYSAyRCBzcGF0aWFsIGFuYWx5c2lzIGxpa2UgdGhlIG9uZSB3ZSdyZSBkb2luZyBoZXJlLCBJIHByZWZlciBhIGhleGFnb24gbGF0dGljZSBvdmVyIGEgc3F1YXJlIGxhdHRpY2UgYmVjYXVzZSBpdCBoYXMgYSBtb3JlIHN5bW1ldHJpY2FsIGFkamFjZW5jeSBtYXRyaXguIEluIGEgc3F1YXJlIGxhdHRpY2UsIHRoZSBkaWFnb25hbHMgYmV0d2VlbiBjZWxscyBhcmUgbG9uZ2VyIHRoYW4gdGhlIGhvcml6b250YWwgYW5kIHZlcnRpY2FsIGRpc3RhbmNlcy4gSW4gaGV4YWdvbmFsIGdyaWRzLCBhbGwgYWRqYWNlbmN5IGRpc3RhbmNlcyBhcmUgZXF1YWwuIFRoaXMgY29kZSBjcmVhdGVzIHRoZSBoZXggZ3JpZCB0byBiZSB1c2VkIGJlbG93IGZvciBzYW1wbGluZy4gVGhlIGV4dGVudCBvZiB0aGlzIGdyaWQgaXMgc2V0IGJ5IHRoZSBCb3VsZGVyIENvdW50eSBib3VuZGluZyBib3ggYW5kIGhlIHJlc29sdXRpb24gd2FzIHNldCB0byBiZSByYXRoZXIgY291cnNlIHRvIHNwZWVkIHVwIGNvbXB1dGF0aW9uIHRpbWUgYW5kIGtlZXAgdGhpcyBleGFtcGxlIGxpZ2h0IGFuZCBmYXN0LiAgICAKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsICBmaWcuY2FwPSJGcmVzaGx5IGNvbnN0cnVjdGVkIGhleGFnb25hbCBzYW1wbGluZyBncmlkIn0KIyBEZWZpbmUgYm91bmRpbmcgYm94CiBleHQgPC0gYXMoZXh0ZW50KGdldGJiICgiQm91bGRlciBDb3VudHkgQ29sb3JhZG8iKSkgLCAiU3BhdGlhbFBvbHlnb25zIikKCiMgU2V0IHNwYXRpYWwgcHJvamVjdGlvbgogIGNycyhleHQpIDwtICJFUFNHOjQzMjYiCiAgCiMgVXNlIHNwc2FtcGxlIHRvIG1lYXN1cmUgb3V0IGEgZ3JpZCBvZiBjZW50ZXIgcG9pbnRzIHdpdGhpbiB0aGUgYm91bmRpbmcgYm94ICAKICBoIDwtIHNwc2FtcGxlKGV4dCwgdHlwZSA9ICJoZXhhZ29uYWwiLCBjZWxsc2l6ZSA9IDAuMDEpCiAgICAKIyBjb252ZXJ0IGNlbnRlciBwb2ludHMgdG8gaGV4YWdvbnMKICBnIDwtIEhleFBvaW50czJTcGF0aWFsUG9seWdvbnMoaCwgZHggPSAwLjAxKQogIAojIFJlZm9ybWF0IGZvciBlYXN5IHBsb3R0aW5nCiAgZyA8LSBzdF9hc19zZihnKQogCiMgUGxvdCB0byBjaGVjayB0aGF0IGl0J3MgYnVpbHQgY29ycnJlY3RseSAgCiAgcGxvdChnLCBsd2Q9MC4xKQogIAojIE1ha2UgdGhlIGRhdGFzZXQgbW9yZSBwcmVzZW50YWJsZQogIGhleF9wb2x5cyA8LSBjYmluZChzZXEoMSwgbGVuZ3RoKGckZ2VvbWV0cnkpKSwgZykKICBjb2xuYW1lcyhoZXhfcG9seXMpIDwtIGMoImlkX3BvbHlnb25zIiwgImdlb21ldHJ5IikgIyBjaGFuZ2UgY29sbmFtZXMKICAKYGBgCiMjIyBBZ2dyZWdhdGUgaU5hdHVyYWxpc3QgZGF0YSBpbnRvIGhleCBiaW5zCiAgTm93IHRoYXQgd2UgaGF2ZSBpTmF0dXJhbGlzdCBkYXRhIGZvcm1hdHRlZCBhcyBwb2ludHMgYW5kIGEgaGV4IGdyaWQgYnVpbHQgZm9yIHNhbXBsaW5nIHRob3NlIHBvaW50cywgaXQncyB0aW1lIHRvIGJpbiB0aG9zZSBwb2ludHMgaW50byB0aGVpciBhcHByb3ByaWF0ZSBoZXhhZ29ucy4gSSBkbyB0aGlzIHVzaW5nIGFuIGludGVyc2VjdCBmdW5jdGlvbiB0aGF0IGJ1aWxkcyBhIGRhdGFmcmFtZSBieSBhc3NpZ25pbmcgZWFjaCBvY2N1cnJlbmNlIHBvaW50IHRvIHRoZSBoZXhhZ29uYWwgcG9seWdvbiB0aGF0IGVuY29tcGFzc2VzIGl0J3MgY29vcmRpbmF0ZXMuIEkgdGhlbiBncm91cCB0aG9zZSBkYXRhIGJ5IHRoZWlyIG5ldyBwb2x5Z29uIGlkZW50aWZpZXIgYW5kIHBsb3QgdGhlIGhleGFnb24gZ3JpZCB3aXRoIGEgZmlsbCBjb2xvciBncmFkaWVudCByZXByZXNlbnRpbmcgdGhlIG51bWJlciBvZiB1bmlxdWUgb2NjdXJyZW5jZSBwb2ludHMgaW4gdGhhdCBwb2x5Z29uLiAgICAKICAKIyMjIyBJbnNwZWN0IHRoZSBwb2ludHMgb3ZlcmxheWluZyB0aGUgZ3JpZC4KCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBmaWcuY2FwPSJIZXhhZ29uIGdyaWQgbGFpZCBvdmVyIGEgZGlzdHJpYnV0aW9uIG9mIGlOYXR1cmFsaXN0IHBvaW50cyJ9CnN0X2NycyhzdF9HQklGKSA8LSBjcnMoaGV4X3BvbHlzKQppbnRlcnNlY3Rpb24gPC0gc3RfaW50ZXJzZWN0aW9uKHggPSBoZXhfcG9seXMsIHkgPSBzdF9HQklGKQoKaW50X3Jlc3VsdCA8LSBpbnRlcnNlY3Rpb24gJT4lIAogIGdyb3VwX2J5KGlkX3BvbHlnb25zKSAKCnBsb3QoZywgbHdkPTAuMSkKcGxvdChpbnRfcmVzdWx0JGdlb21ldHJ5LCBwY2g9MTksIGNleD0wLjI1LCBjb2w9YWRqdXN0Y29sb3IoImNvcm5mbG93ZXJibHVlIiwgYWxwaGEuZiA9IDAuMiksIGFkZD1UUlVFKQpgYGAKCgojIyMjIEFnZ3JpZ2F0ZSBwb2ludHMgaW50byBoZXggYmlucwoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGZpZy5jYXA9ImlOYXR1cmFsaXN0IG9jY3VyYW5jZSBwb2ludHMgYWdncmlnYXRlZCBpbnRvIGhleCBiaW5zLiBUaGUgY29sb3Igb2YgdGhlIGJpbiByZXByZXNlbnRzIHRoZSBudW1iZXIgb2YgZGlmZmVyZW50IHNwZWNpZXMgZGV0ZWN0ZWQgaW4gdGhhdCBoZXhhZ29uLiJ9CgojIENyZWF0ZSBzdG9yYWdlIGRldmljZQpzcGVjaWVzX3JpY2huZXNzIDwtIHJlcCgwLCBtYXgoaGV4X3BvbHlzJGlkX3BvbHlnb25zKSkKaGV4X2NvdW50cyA8LSBjYmluZChoZXhfcG9seXMsIHNwZWNpZXNfcmljaG5lc3MpCgojIENvdW50IHBvaW50cyBwZXIgaGV4YWdvbgppbnRfY291bnQgPC0gaW50ZXJzZWN0aW9uICU+JSAKICBncm91cF9ieShpZF9wb2x5Z29ucywgLmRyb3AgPSBGQUxTRSkgJT4lIAogIGNvdW50KCkKCiMgQWRkIGNvdW50cyB0byBzdG9yYWdlIGRldmljZQpoZXhfY291bnRzJHNwZWNpZXNfcmljaG5lc3NbaW50X2NvdW50JGlkX3BvbHlnb25zXSA8LSBpbnRfY291bnQkbgoKIyBQbG90CnBsb3QoaGV4X2NvdW50cyRnZW9tZXRyeSwgbHdkPTAuMDAxLCAKICAgICBjb2w9Z3JleS5jb2xvcnMobWF4KGhleF9jb3VudHMkc3BlY2llc19yaWNobmVzcyksIHJldiA9IFRSVUUsIHN0YXJ0PTAsIGVuZD0xKVtoZXhfY291bnRzJHNwZWNpZXNfcmljaG5lc3MrMV0pCnBsb3QoaW50X3Jlc3VsdCRnZW9tZXRyeSwgcGNoPTE5LCBjZXg9MC4wNSwgY29sPWFkanVzdGNvbG9yKCJjb3JuZmxvd2VyYmx1ZSIsIGFscGhhLmYgPSAwLjIpLCBhZGQ9VFJVRSkKYGBgCiMjIyMgUGxvdCBzcGVjaWVzIHJpY2huZXNzIHVzaW5nIGEgZmFuY3kgY29sb3Igc2NoZW1lIHRoYXQgd29ya3Mgd2VsbCBmb3IgY29sb3ItYmxpbmQgcGVvcGxlLiAKICBBIGNvdW50IG9mIHRoZSBudW1iZXIgb2Ygc3BlY2llcyBpbiBhbiBhcmVhIGlzIGNhbGxlZCBzcGVjaWVzIHJpY2huZXNzLiBTcGVjaWVzIHJpY2huZXNzIGlzIGEgY29tbW9uIHByb3h5IHZhcmlhYmxlIHVzZWQgdG8gZGVzY3JpYmUgZGlmZmVyZW5jZXMgYmV0d2VlbiBlY29zeXN0ZW1zIG9yIGhlYWx0aCB3aXRoaW4gYSBzaW5nbGUgZWNvc3lzdGVtLiBJdCBwcm92aWRlcyBhbiBpbmNvbXBsZXRlIGRlc2NyaXB0aW9uIG9mIGFuIGVjb3N5c3RlbSwgYnV0IHNwZWNpZXMgcmljaG5lc3MgaXMgc3RpbGwgZnJlcXVlbnRseSB1c2VkIGFzIGEgZ29vZCBpbmRpY2F0b3Igb2YgZWNvc3lzdGVtIHR5cGUgYW5kIGhlYWx0aCBiZWNhdXNlIG1vcmUgY29tcGxleCBlY29zeXN0ZW1zIGhhdmUgbW9yZSBzcGVjaWVzIGFuZCBzbywgdGhlIG51bWJlciBvZiBzcGVjaWVzIGNhbiBvZnRlbiBhcHByb3hpbWF0ZSBjb21wbGV4aXR5IGFuZCBjb21wbGV4aXR5IGlzIGEgZGVmaW5pbmcgZmVhdHVyZSBvZiBlY29zeXN0ZW1zLiAKCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBmaWcuY2FwPSJTcGVjaWVzIHJpY2huZXNzIG9mIGlOYXR1cmFsaXN0IHJlY29yZHMgaW4gMjAyMSJ9CnBsb3QoaGV4X2NvdW50cyRnZW9tZXRyeSwgbHdkPTAuMDAxLCAKICAgICBjb2w9aGNsLmNvbG9ycyhtYXgoaGV4X2NvdW50cyRzcGVjaWVzX3JpY2huZXNzKSsyLCBwYWxldHRlID0gInZpcmlkaXMiLCBhbHBoYSA9IE5VTEwsIHJldiA9IFRSVUUsIGZpeHVwID0gVFJVRSlbaGV4X2NvdW50cyRzcGVjaWVzX3JpY2huZXNzKzFdKQpgYGAKIyMjIEFkamFjZW5jeSBtYXRyaXggCiAgV2UgbmVlZCB0byBpbmNsdWRlIGEgbGlzdCBvZiBuZWlnaGJvciByZWxhdGlvbnNoaXBzIGFzIGEgcmFuZG9tIGNvLXZhcmlhdGUgaW4gb3VyIG1vZGVsLiBIZXIgd2UgY2FsY3VsYXRlIHRoYXQgYWRqYWNlbmN5IG1hdHJpeCB1c2luZyBhIGZ1bmN0aW9uIHRoYXQgdGFrZXMgYSBsaXN0IG9mIHBvbHlnb25zIGFuZCByZXR1cm5zIHRoZSBhZGphY2VuY3kgbWF0cml4LiBJIGdpdmUgdGhpcyBmdW5jdGlvbiB0aGUgaGV4YWdvbiBncmlkLCB3aGljaCBpcyBidWlsdCBvZiBoZXhhZ29uYWwgcG9seWdvbnMgd2l0aCBjZW50cm9pZHMsIGFuZCB0aGUgZnVuY3Rpb24gY2FsY3VsYXRlcyB0aGUgYWRqYWNlbmN5IHJlbGF0aW9uc2hpcHMgZm9yIGFsbCB0aG9zZSBjZW50cm9pZHMuIApgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KCmhleF9hZGogPC0gcG9seTJuYihhcyhoZXhfY291bnRzLCAiU3BhdGlhbCIpICkKCnBsb3QoaGV4X2NvdW50cyRnZW9tZXRyeSwgbHdkPTAuMDAxLCAKICAgICBjb2w9aGNsLmNvbG9ycyhtYXgoaGV4X2NvdW50cyRzcGVjaWVzX3JpY2huZXNzKSsyLCBwYWxldHRlID0gInZpcmlkaXMiLCBhbHBoYSA9IE5VTEwsIHJldiA9IFRSVUUsIGZpeHVwID0gVFJVRSlbaGV4X2NvdW50cyRzcGVjaWVzX3JpY2huZXNzKzFdKQpwbG90KGhleF9hZGosIGNvb3JkaW5hdGVzKGFzKGhleF9jb3VudHMsICJTcGF0aWFsIikpLCBjb2w9ImRhcmtibHVlIiwgbHdkPTAuMiwgYWRkPVRSVUUpCmBgYAoKIyMjIENvLXZhcmlhdGVzCiAgRGF0YSBkb24ndCBwcm9kdWNlIHRoZW1zZWx2ZXMgaW4gYSB2YWN1dW0sIHRoZXkgYXJlIGNyZWF0ZWQgYnkgYSBwcm9jZXNzIGFuZCB0aGF0IHByb2Nlc3MgaW52b2x2ZXMgbWFueSBwYXJ0cy4gSSBhbSBub3cgZ29pbmcgdG8gb3JnYW5pemUgc29tZSBkYXRhIHRvIGFjdCBhcyBjby12YXJpYXRlcyBpbiBhIG1vZGVsIHByZWRpY3RpbmcgaU5hdHVyYWxpc3Qgc3BlY2llcyByaWNobmVzcy4gVGhlc2UgdmFyaWFibGVzIHNob3VsZCByZWxhdGUgdG8gaHlwb3RoZXNlcyBhYm91dCBtZWNoYW5pc21zIHRoYXQgY3JlYXRlZCB0aGVzZSBkYXRhIGFuZCB0aGV5IHNob3VsZCBiZSBhdCB0aGUgc2FtZSBzcGF0aWFsIGFuZCB0ZW1wb3JhbCBzY2FsZSBhcyBvdGhlciBkYXRhIGluIHRoZSBhbmFseXNpcy4KICAKIyMjIyBPcGVuIFN0cmVldCBNYXAgZGF0YQogIE9uIG9mIG15IGZhdm9yaXRlIGRhdGFzZXRzIHRvIHVzZSBvbiBhIGRhaWx5IGJhc2lzIGlzIE9wZW4gU3RyZWV0IE1hcC4gSXQgaXMgYW4gYW1hemluZyByZXNvdXJjZSBmb3IgYSBzdWl0ZSBvZiBkaWZmZXJlbnQgZGF0YSBhYm91dCBsYW5kIHVzZSwgYW1lbml0aWVzLCBhZG1pbmlzdHJhdGl2ZSBib3VuZGFyaWVzLCBwb2ludHMgb2YgaW50ZXJlc3QsIHJvdXRpbmcsIGFuZCBjb25zdHJ1Y3Rpb24uIFRoZSBjb2RlIGJlbG93IHB1bGxzIHBvbHlnb25zIGFuZCBtdWx0aXBvbHlnb25zIGZyb20gdGhlIE9TTSBBUEkgYmFzZWQgb24gdmFsdWUva2V5IHBhaXJzLiBJIHBpY2tlZCBlYWNoIHZhbHVlL2tleSBwYWlyIHVzaW5nIHRoZSBPU00gdGFncyB3aWtpIHdlYnNpdGUsIHdoaWNoIGNhdGFsb2dzIGFsbCB0aGUgYXZhaWxhYmxlIGRhdGEgaW4gT1NNLiBJIHNlYXJjaCBmb3IgZGlmZmVyZW50IHRhZ3MgdGhhdCBkZXNjcmliZWQgZ3JlZW4gc3BhY2UgYW5kIEkgd3JvdGUgYSBuZXcgQVBJIGNhbGwgZm9yIGVhY2ggdmFsdWUva2V5IHBhaXIgdGhhdCBJIHRob3VnaHQgYXBwbGljYWJsZS4gSSBjb25jYXRlbmF0ZSB0aG9zZSBpbmRpdmlkdWFsIGRhdGEgcmVxdWVzdHMgaW50byBhIHNpbmdsZSBncmVlbiBzcGFjZSBkYXRhc2V0LiBJIHRoZW4gbWFrZSB0aHJlZSBtb3JlIGluZGl2aWR1YWwgY2FsbHMgdG8gcmV0cml2ZSBkYXRhIGZvciBidWlsZGluZ3MsIHJvYWRzLCBhbmQgYWdyaWN1bHR1cmFsIGZpZWxkcy4gSSB0aGluayBlYWNoIG9mIHRoZXNlIG1heSBiZSBjb250cmlidXRpbmcgdG8gb3VyIHJlc3BvbnNlIHZhcmlhYmxlIHNvLCBJIGhhdmUgbGVmdCBhcyBpbmRpdmlkdWFsIGRhdGFmcmFtZXMgc28gdGhleSBjYW4gYmUgdHJlYXRlZCBpbmRpdmlkdWFsbHkgaW4gdGhlIG1vZGVsaW5nIHByb2Nlc3MuICAKCmBgYHtyfQpuYW1lIDwtICJCb3VsZGVyIENvdW50eSBDb2xvcmFkbyIKCnRyeShteV9iYm94IDwtIG9zbWRhdGE6OmdldGJiIChuYW1lKSkKCm15X2Jib3hbMSwxXSA8LSBteV9iYm94WzEsMV0gKyAwLjAyCgoKZGF0MSA8LSBvcHEoYmJveCA9IG15X2Jib3gsIHRpbWVvdXQgPSA5MDApICU+JQogICAgYWRkX29zbV9mZWF0dXJlKGtleSA9ICdsZWlzdXJlJywgdmFsdWUgPSAncGFyaycpICU+JQogICAgb3NtZGF0YV9zZiAoKQpkYXQyIDwtIG9wcShiYm94ID0gbXlfYmJveCwgdGltZW91dCA9IDkwMCkgJT4lCiAgICBhZGRfb3NtX2ZlYXR1cmUoa2V5ID0gJ2xlaXN1cmUnLCB2YWx1ZSA9ICdnYXJkZW4nKSAlPiUKICAgIG9zbWRhdGFfc2YgKCkKZGF0MyA8LSBvcHEoYmJveCA9IG15X2Jib3gsIHRpbWVvdXQgPSA5MDApICU+JQogICAgYWRkX29zbV9mZWF0dXJlKGtleSA9ICdsZWlzdXJlJywgdmFsdWUgPSAnbmF0dXJlX3Jlc2VydmUnKSAlPiUKICAgIG9zbWRhdGFfc2YgKCkKZGF0NCA8LSBvcHEoYmJveCA9IG15X2Jib3gsIHRpbWVvdXQgPSA5MDApICU+JQogICAgYWRkX29zbV9mZWF0dXJlKGtleSA9ICdsZWlzdXJlJywgdmFsdWUgPSAnZG9nX3BhcmsnKSAlPiUKICAgIG9zbWRhdGFfc2YgKCkKZGF0NSA8LSBvcHEoYmJveCA9IG15X2Jib3gsIHRpbWVvdXQgPSA5MDApICU+JQogICAgYWRkX29zbV9mZWF0dXJlKGtleSA9ICdsZWlzdXJlJywgdmFsdWUgPSAnY29tbW9uJykgJT4lCiAgICBvc21kYXRhX3NmICgpCmRhdDYgPC0gb3BxKGJib3ggPSBteV9iYm94LCB0aW1lb3V0ID0gOTAwKSAlPiUKICAgIGFkZF9vc21fZmVhdHVyZShrZXkgPSAnbGVpc3VyZScsIHZhbHVlID0gJ3BpdGNoJykgJT4lCiAgICBvc21kYXRhX3NmICgpCmRhdDcgPC0gb3BxKGJib3ggPSBteV9iYm94LCB0aW1lb3V0ID0gOTAwKSAlPiUKICAgIGFkZF9vc21fZmVhdHVyZShrZXkgPSAnbGVpc3VyZScsIHZhbHVlID0gJ2hvcnNlX3JpZGluZycpICU+JQogICAgb3NtZGF0YV9zZiAoKQpkYXQ4IDwtIG9wcShiYm94ID0gbXlfYmJveCwgdGltZW91dCA9IDkwMCkgJT4lCiAgICBhZGRfb3NtX2ZlYXR1cmUoa2V5ID0gJ25hdHVyYWwnLCB2YWx1ZSA9ICd3b29kJykgJT4lCiAgICBvc21kYXRhX3NmICgpCmdyZWVucyA8LSBjIChkYXQxLCBkYXQyLCBkYXQzLCBkYXQ0LCBkYXQ1LCBkYXQ2LCBkYXQ3LCBkYXQ4KQoKCmJ1aWxkaW5ncyA8LSBvcHEoYmJveCA9IG15X2Jib3gsIHRpbWVvdXQgPSA5MDApICU+JQogICAgYWRkX29zbV9mZWF0dXJlKGtleSA9ICdidWlsZGluZycpICU+JQogICAgb3NtZGF0YV9zZiAoKQoKcm9hZHMgPC0gb3BxKGJib3ggPSBteV9iYm94LCB0aW1lb3V0ID0gOTAwKSAlPiUKICAgIGFkZF9vc21fZmVhdHVyZShrZXkgPSAnbGFuZHVzZScsIHZhbHVlID0gJ3Jlc2lkZW50aWFsJykgJT4lCiAgICBvc21kYXRhX3NmICgpCgphZ3JpY3VsdHVyZSA8LSBvcHEoYmJveCA9IG15X2Jib3gsIHRpbWVvdXQgPSA5MDApICU+JQogICAgYWRkX29zbV9mZWF0dXJlKGtleSA9ICdsYW5kdXNlJywgdmFsdWUgPSAnZmFybWxhbmQnKSAlPiUKICAgIG9zbWRhdGFfc2YoKQpgYGAKCgojIyMjIFBsb3QgdGhlIGdyZWVuIHNwYWNlIGRhdGEgZG93bmxvYWRlZCBmcm9tIE9TTSBhZ2FpbnN0IHRoZSBzcGVjaWVzIHJpY2huZXNzIGRhdGEgdG8gbWFrZSBzdXJlIHRoZXkgb3ZlcmxhcCB3ZWxsLgpgYGB7cn0KcGxvdChoZXhfY291bnRzJGdlb21ldHJ5LCBsd2Q9MC4wMDEsIAogICAgIGNvbD1oY2wuY29sb3JzKG1heChoZXhfY291bnRzJHNwZWNpZXNfcmljaG5lc3MpLCBwYWxldHRlID0gInZpcmlkaXMiLCBhbHBoYSA9IDAsIHJldiA9IFRSVUUsIGZpeHVwID0gVFJVRSlbaGV4X2NvdW50cyRzcGVjaWVzX3JpY2huZXNzKzFdKQpwbG90KGdyZWVucyRvc21fcG9seWdvbnNbMV0sIGFkZD1UUlVFLCBsd2Q9MC4wMSwgY29sPWFkanVzdGNvbG9yKCJncmV5IiwgYWxwaGEuZiA9IDEpKQpwbG90KGdyZWVucyRvc21fbXVsdGlwb2x5Z29uc1sxXSwgYWRkPVRSVUUsIGx3ZD0wLjAxLCBjb2w9YWRqdXN0Y29sb3IoImdyZXkiLCBhbHBoYS5mID0gMSkpCiNwbG90KGJ1aWxkaW5ncyRvc21fcG9seWdvbnNbMV0sIGFkZD1UUlVFLCBsd2Q9MC4wMSwgY29sPWFkanVzdGNvbG9yKCJibGFjayIsIGFscGhhLmYgPSAxKSkKcGxvdChoZXhfY291bnRzJGdlb21ldHJ5LCBsd2Q9MC4wMDEsIAogICAgIGNvbD1oY2wuY29sb3JzKG1heChoZXhfY291bnRzJHNwZWNpZXNfcmljaG5lc3MpKzIsIHBhbGV0dGUgPSAidmlyaWRpcyIsIGFscGhhID0gMC44LCByZXYgPSBUUlVFLCBmaXh1cCA9IFRSVUUpW2hleF9jb3VudHMkc3BlY2llc19yaWNobmVzcysxXSwgYWRkPVRSVUUpCmBgYAoKIyMjIEFnZ3JlZ2F0ZSBjb3ZhcmlhdGVzIHRvIGhleCBiaW5zIHVzaW5nIHRoZSBzYW1lIG1ldGhvZCBhcyB3ZSBkaWQgd2l0aCBzcGVjaWVzIHJpY2huZXNzCiAgQWdncmVnYXRpbmcgZGF0YSB0byBoZXhhZ29uIGJpbnMgd2lsbCBiZSBhIHJlcXVpcmVkIHByb2NlZHVyZSBmb3IgYWxsIGNvdmFyaWF0ZXMsIHdoaWNoIG1lYW5zIHdlIHdpbGwgbmVlZCB0byByZXBlYXQgdGhpcyBwcm9jZWR1cmUgYW5kIGl0IG1ha2Ugc2Vuc2UgdG8gc3RvcCBhbmQgbWFrZSBhIGZ1bmN0aW9uIHRvIGtlZXAgb3VyIGNvZGUgY2xlYW4uIFRoZSBmaXJzdCBvZiB0aGVzZSBmdW5jdGlvbnMgY2FsY3VsYXRlcyBwb2x5Z29uIGFyZWEgZm9yIHRoZSBncmVlbnNwYWNlIHBvbHlnb25zIGFuZCBhZGRzIHRoYXQgYXJlYSBjYWxjdWxhdGlvbiBiYWNrIGludG8gdGhlIGRhdGFzZXQgYXMgYSBjb3ZhcmlhdGUuIApgYGB7cn0KY292YXJpYXRlX2hleF9ncmVlbnMgPC0gZnVuY3Rpb24oc2Zfb2JqZWN0KXsKCmlmKGxlbmd0aChzZl9vYmplY3Qkb3NtX211bHRpcG9seWdvbnMpID4gMCl7CiAgcG9seV9hcmVhIDwtIHN0X2FyZWEoc2Zfb2JqZWN0JG9zbV9wb2x5Z29ucykKbXVsdGlfcG9seV9hcmVhIDwtIHN0X2FyZWEoc2Zfb2JqZWN0JG9zbV9tdWx0aXBvbHlnb25zKQpsb2dfYXJlYTEgPC0gYXMuZGF0YS5mcmFtZShhcy5udW1lcmljKGxvZyhwb2x5X2FyZWEpKSkKbG9nX2FyZWEyIDwtIGFzLmRhdGEuZnJhbWUoYXMubnVtZXJpYyhsb2cobXVsdGlfcG9seV9hcmVhKSkpCm5hbWVzKGxvZ19hcmVhMSkgPC0gImxvZ19hcmVhIgpuYW1lcyhsb2dfYXJlYTIpIDwtICJsb2dfYXJlYSIKbG9nX2FyZWEgPC0gcmJpbmQobG9nX2FyZWExLCBsb2dfYXJlYTIpIAoKY29vcmRzIDwtIHJiaW5kKHNmX29iamVjdCRvc21fcG9seWdvbnMgJT4lIHN0X2NlbnRyb2lkKCkgJT4lIHN0X2Nvb3JkaW5hdGVzKCksIAogICAgICAgICAgICAgICAgc2Zfb2JqZWN0JG9zbV9tdWx0aXBvbHlnb25zICU+JSBzdF9jZW50cm9pZCgpICU+JSBzdF9jb29yZGluYXRlcygpKQp9IGVsc2UgewogIHBvbHlfYXJlYSA8LSBzdF9hcmVhKHNmX29iamVjdCRvc21fcG9seWdvbnMpCmxvZ19hcmVhIDwtIGFzLmRhdGEuZnJhbWUoYXMubnVtZXJpYyhsb2cocG9seV9hcmVhKSkpCm5hbWVzKGxvZ19hcmVhKSA8LSAibG9nX2FyZWEiCgpjb29yZHMgPC0gc2Zfb2JqZWN0JG9zbV9wb2x5Z29ucyAlPiUgc3RfY2VudHJvaWQoKSAlPiUgc3RfY29vcmRpbmF0ZXMoKQp9CgppZihsZW5ndGgoY29vcmRzWywxXSkgIT0gbGVuZ3RoKGxvZ19hcmVhWywxXSkpIHN0b3AoIndyb25nIGxlbmd0aCIpCmFyZWFfcG9pbnRzIDwtIGNiaW5kKGxvZ19hcmVhLCBjb29yZHMpCmNvbG5hbWVzKGFyZWFfcG9pbnRzKVsyOjNdIDwtIGMoImxvbmdpdHVkZSIsICJsYXRpdHVkZSIpCnNmX2FyZWFfcG9pbnRzIDwtIHN0X2FzX3NmKGFyZWFfcG9pbnRzLCBjb29yZHMgPSBjKCJsb25naXR1ZGUiLCAibGF0aXR1ZGUiKSwgCiAgICBjcnMgPSA0MzI2LCBhZ3IgPSAiY29uc3RhbnQiKQoKcmV0dXJuKHNmX2FyZWFfcG9pbnRzKQp9CmBgYAoKVGhlIHNlY29uZCBmdW5jdGlvbiBkb2VzIHRoZSBzYW1lIGFnZ3JlZ2F0aW9uIHByb2NlZHVyZSBhcyBJIGRpZCB3aXRoIHNwZWNpZXMgcmljaG5lc3MsIHdoaWNoIGlzIHRvIHNheSB0aGF0IGl0IGNvdW50cyB0aGVuIG51bWJlciBvZiBvY2N1cnJlbmNlcyB3aXRoaW4gYSBiaW4gYW5kIGFzc2lnbnMgdGhhdCBiaW4gYSB2YWx1ZSBmb3IgdGhhdCBjb3VudC4gVGhlIG90aGVyIGZ1bmN0aW9uIGluIHRoaXMgY2h1bmsgaXMgYSBjbG9uZSBvZiB0aGUgY291bnRpbmcgZnVuY3Rpb24gbW9kaWZpZWQgdG8gcmVwb3J0IG1lYW4gcmF0aGVyIHRoYW4gY291bnQuIFRoZSBtZWFuIHZlcnNpb24gb2YgdGhlIGZ1bmN0aW9uIGlzIHZhbHVhYmxlIG9yIHRoZSBncmVlbnNwYWNlIGFyZWEgZGF0YSB3aGVyZSByZXBvcnRpbmcgdGhlIG1lYW4gYXJlYSBvZiBncmVlbnNwYWNlIGlmIG1vcmUgaW5mb3JtYXRpdmUgdGhhbiB0aGUgY291bnQgb2YgcGFya3MgaW4gdGhhdCBiaW4uIApgYGB7cn0KIyBDb3VudCB0aGUgbnVtYmVyIG9mIHBvaW50cyBpbiBlYWNoIGJpbgpwb2ludF90b19oZXhfY291bnQgPC0gZnVuY3Rpb24oaGV4X3BvbHlzLCBwb2ludHMpewppbnRlcnNlY3Rpb24gPC0gc3RfaW50ZXJzZWN0aW9uKHggPSBoZXhfcG9seXMsIHkgPSBwb2ludHMpCgpjb3VudGVyIDwtIHJlcCgwLCBtYXgoaGV4X3BvbHlzJGlkX3BvbHlnb25zKSkKaGV4X2NvdW50IDwtIGNiaW5kKGhleF9wb2x5cywgY291bnRlcikKCmNvdW50ZWQgPC0gaW50ZXJzZWN0aW9uICU+JSAKICBncm91cF9ieShpZF9wb2x5Z29ucywgLmRyb3AgPSBGQUxTRSkgJT4lIAogIGNvdW50KCkKCmhleF9jb3VudCRhcmVhW2NvdW50ZWQkaWRfcG9seWdvbnNdIDwtIGNvdW50ZWQkbgoKcmV0dXJuKGhleF9jb3VudCkKfQoKCiMgUmVwb3J0IHRoZSBtZWFuIHZhbHVlIGZvciBlYWNoIGJpbgpwb2ludF90b19oZXhfbWVhbiA8LSBmdW5jdGlvbihoZXhfcG9seXMsIHBvaW50cyl7CmludGVyc2VjdGlvbiA8LSBzdF9pbnRlcnNlY3Rpb24oeCA9IGhleF9wb2x5cywgeSA9IHBvaW50cykKCmFyZWEgPC0gcmVwKDAsIG1heChoZXhfcG9seXMkaWRfcG9seWdvbnMpKQpoZXhfYXJlYSA8LSBjYmluZChoZXhfcG9seXMsIGFyZWEpCgphdmdfYXJlYSA8LSBpbnRlcnNlY3Rpb24gJT4lIAogIGdyb3VwX2J5KGlkX3BvbHlnb25zLCAuZHJvcCA9IEZBTFNFKSAlPiUgCiAgc3VtbWFyaXNlKG1lYW4gPSBzdW0obG9nX2FyZWEsIG5hLnJtPVRSVUUpKQoKaGV4X2FyZWEkYXJlYVthdmdfYXJlYSRpZF9wb2x5Z29uc10gPC0gYXZnX2FyZWEkbWVhbgoKcmV0dXJuKGhleF9hcmVhKQp9CgpgYGAKCiMjIyMgUnVuIHRob3NlIGZ1bmN0aW9ucyBvbiBvdXIgZ3JlZW4gc3BhY2UgZGF0YSB0byBwcm9kdWNlIGEgcGxvdCBvZiBncmVlbiBzcGFjZSBhcmVhIGludGVuc2l0eQogIFRoZSBwbG90IGJlbG93IHNob3cgdGhlIHRvdGFsIGFyZWEgb2YgZ3JlZW5zcGFjZSBwZXIgcG9seWdvbi4gTXkgaHlwb3RoZXNpcyBpcyB0aGF0IHRoZSBwcmVzZW5jZSBvZiBncmVlbiBzcGFjZSBpcyBhIG1ham9yIHByZWRpY3RvciBvZiBpTmF0dXJhbGlzdCBkYXRhIGFuZCB3YW50IHRvIGNvbXBhcmUgdGhpcyBwbG90IHRvIHRoZSBzcGVjaWVzIHJpY2huZXNzIHBsb3QgdG8gc2VlIGl0IGl0IGxvb2tzIGxpa2UgZ3JlZW4gc3BhY2VzIGFuZCBpTmF0dXJhbGlzdCByZWNvcmRzIGFyZSBnZW5lcmFsbHkgbG9jYXRlZCBpbiB0aGUgc2FtZSBwbGFjZXMuCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpncmVlbl9wdHMgPC0gY292YXJpYXRlX2hleF9ncmVlbnMoZ3JlZW5zKQpncmVlbl9oZXggPC0gcG9pbnRfdG9faGV4X21lYW4oaGV4X3BvbHlzLCBncmVlbl9wdHMpCmhleCA8LSBncmVlbl9oZXgKcGxvdF92YXJpYWJsZSA8LSBmdW5jdGlvbihoZXgsIHZhcmlhYmxlICl7CnBsb3QoaGV4JGdlb21ldHJ5LCBsd2Q9MC4wMDEsIAogICAgIGNvbD1oY2wuY29sb3JzKG1heChhcy5pbnRlZ2VyKGdyZWVuX2hleCRhcmVhKSsyLCBuYS5ybSA9IFRSVUUpLCBwYWxldHRlID0gInZpcmlkaXMiLCBhbHBoYSA9IE5VTEwsIHJldiA9IFRSVUUsIGZpeHVwID0gVFJVRSlbYXMuaW50ZWdlcihncmVlbl9oZXgkYXJlYSwgbmEucm09VFJVRSkrMV0pCn0KcGxvdF92YXJpYWJsZShncmVlbl9oZXgsIGdyZWVuX2hleCRhcmVhKQpgYGAKCiMjIyBDb21iaW5lIFNwZWNpZXMgcmljaG5lc3MgYW5kIGNvdmFyaWF0ZXMgaW50byB0aGUgc2FtZSBkYXRhZnJhbWUgZm9yIGFuYWx5c2lzLiAKIEluIHRoZSBwcmV2aW91cyBjb2RlLCBJIGhhdmUgY3JlYXRlZCB0d28gc3BhdGlhbCBvYmplY3RzIHRoYXQgYXJlIGJvdGggaGV4IGdyaWRzIHdpdGggYWdncmVnYXRlZCBkYXRhIGFzc2lnbmVkIHRvIGVhY2ggYmluLiBUbyBjb21iaW5lIHRob3NlIHNldHMsIEkgZmlyc3QgZHJvcCB0aGUgZ2VvbWV0cnkgZnJvbSBvbmUgb2YgdGhlIHR3byBiZWNhdXNlIHRoZSBnZW9tZXRyaWVzIGFyZSBpZGVudGljYWwgYW5kIHRoZXkgd2lsbCBzaG93IHVwIGFzIGR1cGxpY2F0ZXMgaW4gdGhlIGNvbWJpbmVkIGRhdGFzZXQuIEFmdGVyIGRyb3BpbmcgdGhlIGdlb21ldHJ5IGZyb20gb25lLCBJIGRvIGEgbGVmdCBqb2luIHNvIHRoZXkgbWF0Y2ggcG9seWdvbklEIHZhbHVlcyBhbmQgdGhlbiBldmVyeXRoaW5nIHNoYXJlcyB0aGUgc2luZ2xlIHJlbWFpbmluZyBnZW9tZXRyeSBjb2x1bW4uICAKYGBge3J9CmhleF9saXN0IDwtIHN0X2Ryb3BfZ2VvbWV0cnkoaGV4X2NvdW50cykgJT4lIGlubmVyX2pvaW4oYXMuZGF0YS5mcmFtZShoZXhfZ3JlZW5fYXJlYSksIGJ5PSJpZF9wb2x5Z29ucyIpIApjb2xuYW1lcyhoZXhfbGlzdCkgPC0gYygiaWRfcG9seWdvbiIsICJzcGVjaWVzX3JpY2huZXNzIiwgInBhcmtfc2l6ZV9hdmVyYWdlIiwiZ2VvbWV0cnkiKQpoZXhfbGlzdApgYGAKCgojIyMgSU5MQSBCYXllc2lhbiBpbmZlcmVuY2UKCmBgYHtyfQoKaGV4X2xpc3QkbG9nX3BhcmtfYXJlYSA8LSBsb2coaGV4X2xpc3QkcGFya19zaXplX2F2ZXJhZ2UpCmhleF9saXN0W3doaWNoKGlzLmluZmluaXRlKGhleF9saXN0JGxvZ19wYXJrX2FyZWEpID09IFRSVUUpLCJsb2dfcGFya19hcmVhIl0gPC0gMAoKI1JXMmQKbTAucncyZCA8LSBpbmxhKHNwZWNpZXNfcmljaG5lc3MgfiBsb2dfcGFya19hcmVhICsKICAgIGYoaWRfcG9seWdvbiwgbW9kZWwgPSAicncyZCIsIG5yb3cgPTY0LCBuY29sID0gNDEpLAogIGZhbWlseSA9ICJwb2lzc29uIiwgZGF0YSA9IGFzLmRhdGEuZnJhbWUoaGV4X2xpc3QpLAogIGNvbnRyb2wucHJlZGljdG9yID0gbGlzdChjb21wdXRlID0gVFJVRSksCiAgY29udHJvbC5jb21wdXRlID0gbGlzdChkaWMgPSBUUlVFKSApCgpzdW1tYXJ5KG0wLnJ3MmQpCgpoZXhfbGlzdCRSVzJEIDwtIG0wLnJ3MmQkc3VtbWFyeS5maXR0ZWQudmFsdWVzWywgIjAuNXF1YW50Il0KaGV4X2xpc3QkUlcyRHNkIDwtIG0wLnJ3MmQkc3VtbWFyeS5maXR0ZWQudmFsdWVzWywgInNkIl0KYGBgCgoKCiMjIyBQbG90IHRoZSBtZWFuIHByZWRpY3RlZCB2YWx1ZXMgZnJvbSB0aGUgbW9kZWwuIApgYGB7cn0KI3Bsb3RfdmFyaWFibGUoaGV4X2xpc3QsIGZsb29yKGhleF9saXN0JFJXMkQpKQoKbW9kZWxfcHJlZGljdCA8LSBhcy5pbnRlZ2VyKGhleF9saXN0JFJXMkQpCm1vZGVsX3ByZWRpY3Rbd2hpY2goaXMubmEobW9kZWxfcHJlZGljdCkgPT0gVFJVRSldIDwtIDAgCiNtb2RlbF9wcmVkaWN0W3doaWNoKG1vZGVsX3ByZWRpY3QgPCAwKV0gPC0gMCAKI21vZGVsX3ByZWRpY3Rbd2hpY2gobW9kZWxfcHJlZGljdCA+IDIwKV0gPC0gMCAKCnBsb3QoaGV4X2NvdW50cyRnZW9tZXRyeSwgbHdkPTAuMDAxLCAKICAgICBjb2w9aGNsLmNvbG9ycyhtYXgoaGV4X2NvdW50cyRzcGVjaWVzX3JpY2huZXNzKSsyLCBwYWxldHRlID0gInZpcmlkaXMiLCBhbHBoYSA9IE5VTEwsIHJldiA9IFRSVUUsIGZpeHVwID0gRkFMU0UpW2hleF9jb3VudHMkc3BlY2llc19yaWNobmVzcysxXSkKCnBsb3QoaGV4X2xpc3QkZ2VvbWV0cnksIGx3ZD0wLjAwMSwgCiAgICAgY29sPWhjbC5jb2xvcnMobWF4KGhleF9jb3VudHMkc3BlY2llc19yaWNobmVzcykrMiwgcGFsZXR0ZSA9ICJ2aXJpZGlzIiwgYWxwaGEgPSBOVUxMLCByZXYgPSBUUlVFLCBmaXh1cCA9IEZBTFNFKVttb2RlbF9wcmVkaWN0KzFdKQojcGxvdChpbnRfcmVzdWx0JGdlb21ldHJ5LCBwY2g9MTksIGNleD0wLjA1LCBjb2w9YWRqdXN0Y29sb3IoImNvcm5mbG93ZXJibHVlIiwgYWxwaGEuZiA9IDAuMiksIGFkZD1UUlVFKQpgYGAKCgoKYGBge3J9CmJlaS50cmVlczIkUlcyRCA8LSBtMC5ydzJkJHN1bW1hcnkuZml0dGVkLnZhbHVlc1ssICJtZWFuIl0KCmJlaS50cmVlczIkTUFURVJOMkQgPC0gbTAubTJkJHN1bW1hcnkuZml0dGVkLnZhbHVlc1ssICJtZWFuIl0KYGBgCgoKYGBge3J9Cmdfc2YgPC0gc3RfYXNfc2YoZykKSFEgPC0gcmFzdGVyOjpleHRyYWN0KHJhc3Rlcl90aGlzX0hRLCAgZ19zZiAsIGZ1biA9IHN1bSwgbmEucm0gPSBUUlVFLCBkZiA9IFRSVUUsIG5vcm1hbGl6ZVdlaWdodHM9VFJVRSkgIApgYGAKCgo=